package com.ailanyin.modules.job.service.impl;

import cn.hutool.extra.spring.SpringUtil;
import com.ailanyin.common.base.constants.CommonConstant;
import com.ailanyin.common.base.constants.ScheduleConstant;
import com.ailanyin.common.base.exception.Assert;
import com.ailanyin.common.base.utils.StringUtil;
import com.ailanyin.common.security.utils.SecurityUtil;
import com.ailanyin.modules.job.domain.SysJob;
import com.ailanyin.modules.job.exceptions.TaskException;
import com.ailanyin.modules.job.mapper.SysJobMapper;
import com.ailanyin.modules.job.service.SysJobService;
import com.ailanyin.modules.job.utils.CronUtils;
import com.ailanyin.modules.job.utils.ScheduleUtils;
import org.quartz.JobDataMap;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.List;

/**
 * 定时任务调度信息 服务层
 *
 * @author ruoyi
 */
@Service
public class SysJobServiceImpl implements SysJobService {
    @Autowired
    private Scheduler scheduler;

    @Resource
    private SysJobMapper jobMapper;

    /**
     * 项目启动时，初始化定时器 主要是防止手动修改数据库导致未同步到定时任务处理（注：不能手动修改数据库ID和任务组名，否则会导致脏数据）
     */
    @PostConstruct
    public void init() throws SchedulerException, TaskException {
        scheduler.clear();
        List<SysJob> jobList = jobMapper.selectJobAll();
        for (SysJob job : jobList) {
            ScheduleUtils.createScheduleJob(scheduler, job);
        }
    }

    /**
     * 获取quartz调度器的计划任务列表
     *
     * @param job 调度信息
     * @return
     */
    @Override
    public List<SysJob> selectJobList(SysJob job) {
        return jobMapper.selectJobList(job);
    }

    /**
     * 通过调度任务ID查询调度信息
     *
     * @param jobId 调度任务ID
     * @return 调度任务对象信息
     */
    @Override
    public SysJob selectJobById(Long jobId) {
        return jobMapper.selectJobById(jobId);
    }

    /**
     * 暂停任务
     *
     * @param job 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int pauseJob(SysJob job) throws SchedulerException {
        Long jobId = job.getJobId();
        String jobGroup = job.getJobGroup();
        job.setStatus(ScheduleConstant.Status.PAUSE.getValue());
        int rows = jobMapper.updateJob(job);
        if (rows > 0) {
            scheduler.pauseJob(ScheduleUtils.getJobKey(jobId, jobGroup));
        }
        return rows;
    }

    /**
     * 恢复任务
     *
     * @param job 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int resumeJob(SysJob job) throws SchedulerException {
        Long jobId = job.getJobId();
        String jobGroup = job.getJobGroup();
        job.setStatus(ScheduleConstant.Status.NORMAL.getValue());
        int rows = jobMapper.updateJob(job);
        if (rows > 0) {
            scheduler.resumeJob(ScheduleUtils.getJobKey(jobId, jobGroup));
        }
        return rows;
    }

    /**
     * 删除任务后，所对应的trigger也将被删除
     *
     * @param job 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int deleteJob(SysJob job) throws SchedulerException {
        if (job == null || job.getJobId() == null) {
            return 0;
        }
        Long jobId = job.getJobId();
        String jobGroup = job.getJobGroup();
        int rows = jobMapper.deleteJobById(jobId);
        if (rows > 0) {
            JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup);
            // 检查存在
            if (scheduler.checkExists(jobKey)) {
                scheduler.deleteJob(jobKey);
            }
        }
        return rows;
    }

    /**
     * 批量删除调度信息
     *
     * @param jobIds 需要删除的任务ID
     * @return 结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void deleteJobByIds(Long... jobIds) throws SchedulerException {
        for (Long jobId : jobIds) {
            SysJob job = jobMapper.selectJobById(jobId);
            deleteJob(job);
        }
    }

    /**
     * 任务调度状态修改
     *
     * @param job 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int changeStatus(SysJob job) throws SchedulerException {
        int rows = 0;
        String status = job.getStatus();
        if (ScheduleConstant.Status.NORMAL.getValue().equals(status)) {
            rows = resumeJob(job);
        } else if (ScheduleConstant.Status.PAUSE.getValue().equals(status)) {
            rows = pauseJob(job);
        }
        return rows;
    }

    /**
     * 立即运行任务
     *
     * @param job 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void run(SysJob job) throws SchedulerException {
        Long jobId = job.getJobId();
        String jobGroup = job.getJobGroup();
        SysJob properties = selectJobById(job.getJobId());
        // 参数
        JobDataMap dataMap = new JobDataMap();
        dataMap.put(ScheduleConstant.TASK_PROPERTIES, properties);
        scheduler.triggerJob(ScheduleUtils.getJobKey(jobId, jobGroup), dataMap);
    }

    /**
     * 新增
     *
     * @param job 调度信息 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertJob(SysJob job) throws SchedulerException, TaskException {
        checkJobIsNormal(job);
        job.setCreateBy(SecurityUtil.getLoginUsername());
        job.setStatus(ScheduleConstant.Status.PAUSE.getValue());
        int rows = jobMapper.insertJob(job);
        if (rows > 0) {
            ScheduleUtils.createScheduleJob(scheduler, job);
        }
        return rows;
    }

    @Override
    public int insertJobNoInvoke(SysJob job) {
        return jobMapper.insertJob(job);
    }

    /**
     * 更新任务的时间表达式
     *
     * @param job 调度信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateJob(SysJob job) throws SchedulerException, TaskException {
        checkJobIsNormal(job);
        SysJob properties = selectJobById(job.getJobId());
        int rows = jobMapper.updateJob(job);
        if (rows > 0) {
            updateSchedulerJob(job, properties.getJobGroup());
        }
        return rows;
    }

    /**
     * 更新任务
     *
     * @param job      任务对象
     * @param jobGroup 任务组名
     */
    public void updateSchedulerJob(SysJob job, String jobGroup) throws SchedulerException, TaskException {
        checkJobIsNormal(job);
        Long jobId = job.getJobId();
        // 判断是否存在
        JobKey jobKey = ScheduleUtils.getJobKey(jobId, jobGroup);
        if (scheduler.checkExists(jobKey)) {
            // 防止创建时存在数据问题 先移除，然后在执行创建操作
            scheduler.deleteJob(jobKey);
        }
        ScheduleUtils.createScheduleJob(scheduler, job);
    }

    /**
     * 校验cron表达式是否有效
     *
     * @param cronExpression 表达式
     * @return 结果
     */
    @Override
    public boolean checkCronExpressionIsValid(String cronExpression) {
        return CronUtils.isValid(cronExpression);
    }

    private void checkJobIsNormal(SysJob job) {
        String className = getClassName(job.getInvokeTarget());
        if (!CronUtils.isValid(job.getCronExpression())) {
            Assert.fail("新增或修改任务'" + job.getJobName() + "'失败，Cron表达式不正确");
        } else if (StringUtil.containsIgnoreCase(job.getInvokeTarget(), CommonConstant.LOOKUP_RMI)) {
            Assert.fail("新增或修改任务'" + job.getJobName() + "'失败，目标字符串不允许'rmi'调用");
        } else if (StringUtil.containsAnyIgnoreCase(job.getInvokeTarget(), new String[]{CommonConstant.LOOKUP_LDAP, CommonConstant.LOOKUP_LDAPS})) {
            Assert.fail("新增或修改任务'" + job.getJobName() + "'失败，目标字符串不允许'ldap(s)'调用");
        } else if (StringUtil.containsAnyIgnoreCase(job.getInvokeTarget(), new String[]{CommonConstant.HTTP, CommonConstant.HTTPS})) {
            Assert.fail("新增或修改任务'" + job.getJobName() + "'失败，目标字符串不允许'http(s)'调用");
        } else if (StringUtil.containsAnyIgnoreCase(className, ScheduleConstant.JOB_ERROR_STR)) {
            Assert.fail("新增或修改任务'" + job.getJobName() + "'失败，目标字符串存在违规");
        } else if (!StringUtil.containsAnyIgnoreCase(className, ScheduleConstant.JOB_WHITELIST_STR)) {
            Assert.fail("新增或修改任务'" + job.getJobName() + "'失败，目标字符串不在白名单内");
        }
    }

    private String getClassName(String invokeTarget) {
        // invokeTarget = exampleTask.noParam
        String[] split = invokeTarget.split("\\.");
        String beanName = split[0];
        Object bean = null;
        try {
            bean = SpringUtil.getBean(beanName);
        } catch (Exception e) {
            Assert.fail("没有找到Bean: " + beanName);
        }
        return bean.getClass().getName();
    }
}
